"""
ANSI - Gives colour to text.

Use the codes defined in ANSIPARSER in your text
to apply colour to text according to the ANSI standard.

Examples:
 This is %crRed text%cn and this is normal again.
 This is {rRed text{n and this is normal again.

Mostly you should not need to call parse_ansi() explicitly;
it is run by Evennia just before returning data to/from the
user.

"""
import re
from src.utils import utils

# ANSI definitions

ANSI_BEEP = "\07"
ANSI_ESCAPE = "\033"
ANSI_NORMAL = "\033[0m"

ANSI_UNDERLINE = "\033[4m"
ANSI_HILITE = "\033[1m"
ANSI_BLINK = "\033[5m"
ANSI_INVERSE = "\033[7m"
ANSI_INV_HILITE = "\033[1;7m"
ANSI_INV_BLINK = "\033[7;5m"
ANSI_BLINK_HILITE = "\033[1;5m"
ANSI_INV_BLINK_HILITE = "\033[1;5;7m"

# Foreground colors
ANSI_BLACK = "\033[30m"
ANSI_RED = "\033[31m"
ANSI_GREEN = "\033[32m"
ANSI_YELLOW = "\033[33m"
ANSI_BLUE = "\033[34m"
ANSI_MAGENTA = "\033[35m"
ANSI_CYAN = "\033[36m"
ANSI_WHITE = "\033[37m"

# Background colors
ANSI_BACK_BLACK = "\033[40m"
ANSI_BACK_RED = "\033[41m"
ANSI_BACK_GREEN = "\033[42m"
ANSI_BACK_YELLOW = "\033[43m"
ANSI_BACK_BLUE = "\033[44m"
ANSI_BACK_MAGENTA = "\033[45m"
ANSI_BACK_CYAN = "\033[46m"
ANSI_BACK_WHITE = "\033[47m"

# Formatting Characters
ANSI_RETURN = "\r\n"
ANSI_TAB = "\t"
ANSI_SPACE = " "

class ANSIParser(object):
    """
    A class that parses ansi markup
    to ANSI command sequences

    We also allow to escape colour codes
    by prepending with a \.
    """

    def __init__(self):
        "Sets the mappings"

        # MUX-style mappings %cr %cn etc

        self.mux_ansi_map = [
            (r'(?<!\\)%r',  ANSI_RETURN),
            (r'(?<!\\)%t',  ANSI_TAB),
            (r'(?<!\\)%b',  ANSI_SPACE),
            (r'(?<!\\)%cf', ANSI_BLINK),
            (r'(?<!\\)%ci', ANSI_INVERSE),
            (r'(?<!\\)%ch', ANSI_HILITE),
            (r'(?<!\\)%cn', ANSI_NORMAL),
            (r'(?<!\\)%cx', ANSI_BLACK),
            (r'(?<!\\)%cX', ANSI_BACK_BLACK),
            (r'(?<!\\)%cr', ANSI_RED),
            (r'(?<!\\)%cR', ANSI_BACK_RED),
            (r'(?<!\\)%cg', ANSI_GREEN),
            (r'(?<!\\)%cG', ANSI_BACK_GREEN),
            (r'(?<!\\)%cy', ANSI_YELLOW),
            (r'(?<!\\)%cY', ANSI_BACK_YELLOW),
            (r'(?<!\\)%cb', ANSI_BLUE),
            (r'(?<!\\)%cB', ANSI_BACK_BLUE),
            (r'(?<!\\)%cm', ANSI_MAGENTA),
            (r'(?<!\\)%cM', ANSI_BACK_MAGENTA),
            (r'(?<!\\)%cc', ANSI_CYAN),
            (r'(?<!\\)%cC', ANSI_BACK_CYAN),
            (r'(?<!\\)%cw', ANSI_WHITE),
            (r'(?<!\\)%cW', ANSI_BACK_WHITE),
            ]

        # Expanded mapping {r {n etc

        hilite = ANSI_HILITE
        normal = ANSI_NORMAL
        self.ext_ansi_map = [
            (r'(?<!\\){r', hilite + ANSI_RED),
            (r'(?<!\\){R', normal + ANSI_RED),
            (r'(?<!\\){g', hilite + ANSI_GREEN),
            (r'(?<!\\){G', normal + ANSI_GREEN),
            (r'(?<!\\){y', hilite + ANSI_YELLOW),
            (r'(?<!\\){Y', normal + ANSI_YELLOW),
            (r'(?<!\\){b', hilite + ANSI_BLUE),
            (r'(?<!\\){B', normal + ANSI_BLUE),
            (r'(?<!\\){m', hilite + ANSI_MAGENTA),
            (r'(?<!\\){M', normal + ANSI_MAGENTA),
            (r'(?<!\\){c', hilite + ANSI_CYAN),
            (r'(?<!\\){C', normal + ANSI_CYAN),
            (r'(?<!\\){w', hilite + ANSI_WHITE), # pure white
            (r'(?<!\\){W', normal + ANSI_WHITE), #light grey
            (r'(?<!\\){x', hilite + ANSI_BLACK), #dark grey
            (r'(?<!\\){X', normal + ANSI_BLACK), #pure black
            (r'(?<!\\){n', normal)               #reset
            ]

        # xterm256 {123, %c134,

        self.xterm256_map = [
            (r'(?<!\\)%c([0-5]{3})', self.parse_rgb),  # %c123 - foreground colour
            (r'(?<!\\)%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
            (r'(?<!\\){([0-5]{3})', self.parse_rgb),   # {123 - foreground colour
            (r'(?<!\\){(b[0-5]{3})', self.parse_rgb)   # {b123 - background colour
            ]

        # matching for cleaning out escaped colour codes (used with sub)

        self.clean_escapes = [
            (r"\\{", "{"),
            (r"\\%r", "%r"),
            (r"\\%b", "%b"),
            (r"\\%c", "%c")
            ]

        # obs - order matters here, we want to do the xterms first since
        # they collide with some of the other mappings otherwise.
        self.ansi_map = self.xterm256_map + self.mux_ansi_map + self.ext_ansi_map

        # prepare regex matching
        self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
                         for sub in self.ansi_map]

        self.escape_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
                          for sub in self.clean_escapes]

        # prepare matching ansi codes overall
        self.ansi_regex = re.compile("\033\[[0-9;]+m")

    def parse_rgb(self, rgbmatch):
        """
        This is a replacer method called by re.sub with the matched
        tag. It must return the correct ansi sequence.

        It checks self.do_xterm256 to determine if conversion
        to standard ansi should be done or not.
        """
        if not rgbmatch:
            return ""
        rgbtag = rgbmatch.groups()[0]

        background = rgbtag[0] == 'b'
        if background:
            red, green, blue = int(rgbtag[1]), int(rgbtag[2]), int(rgbtag[3])
        else:
            red, green, blue = int(rgbtag[0]), int(rgbtag[1]), int(rgbtag[2])

        if self.do_xterm256:
            colval = 16 + (red * 36) + (green * 6) + blue
            #print "RGB colours:", red, green, blue
            return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval/100, (colval%100)/10, colval%10)
        else:
            #print "ANSI convert:", red, green, blue
            # xterm256 not supported, convert the rgb value to ansi instead
            if red == green and red == blue and red < 2:
                if background: return ANSI_BACK_BLACK
                elif red >= 1: return ANSI_HILITE + ANSI_BLACK
                else: return ANSI_NORMAL + ANSI_BLACK
            elif red == green and red == blue:
                if background: return ANSI_BACK_WHITE
                elif red >= 4: return ANSI_HILITE + ANSI_WHITE
                else: return ANSI_NORMAL + ANSI_WHITE
            elif red > green and red > blue:
                if background: return ANSI_BACK_RED
                elif red >= 3: return ANSI_HILITE + ANSI_RED
                else: return ANSI_NORMAL + ANSI_RED
            elif red == green and red > blue:
                if background: return ANSI_BACK_YELLOW
                elif red >= 3: return ANSI_HILITE + ANSI_YELLOW
                else: return ANSI_NORMAL + ANSI_YELLOW
            elif red == blue and red > green:
                if background: return ANSI_BACK_MAGENTA
                elif red >= 3: return ANSI_HILITE + ANSI_MAGENTA
                else: return ANSI_NORMAL + ANSI_MAGENTA
            elif green > blue:
                if background: return ANSI_BACK_GREEN
                elif green >= 3: return ANSI_HILITE + ANSI_GREEN
                else: return ANSI_NORMAL + ANSI_GREEN
            elif green == blue:
                if background: return ANSI_BACK_CYAN
                elif green >= 3: return ANSI_HILITE + ANSI_CYAN
                else: return ANSI_NORMAL + ANSI_CYAN
            else:    # mostly blue
                if background: return ANSI_BACK_BLUE
                elif blue >= 3: return ANSI_HILITE + ANSI_BLUE
                else: return ANSI_NORMAL + ANSI_BLUE

    def parse_ansi(self, string, strip_ansi=False, xterm256=False):
        """
        Parses a string, subbing color codes according to
        the stored mapping.

        strip_ansi flag instead removes all ansi markup.

        """
        if not string:
            return ''
        string = utils.to_str(string)

        self.do_xterm256 = xterm256
        # handle all subs
        for sub in self.ansi_sub:
            # go through all available mappings and translate them
            string = sub[0].sub(sub[1], string)
        if strip_ansi:
            # remove all ANSI escape codes
            string = self.ansi_regex.sub("", string)
        for sub in self.escape_sub:
            # strip the \ in front of escaped colour codes, like \{r.
            string = sub[0].sub(sub[1], string)
        return string


ANSI_PARSER = ANSIParser()

#
# Access function
#

def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False):
    """
    Parses a string, subbing color codes as needed.

    """
    return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256)


